package org.fjsei.yewu.aop.hibernate;

import org.fjsei.yewu.filter.Node;
import org.hibernate.Session;
import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.document.IndexFieldReference;
import org.hibernate.search.engine.backend.document.IndexObjectFieldReference;
import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField;
import org.hibernate.search.engine.backend.types.Aggregable;
import org.hibernate.search.engine.backend.types.IndexFieldType;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.orm.HibernateOrmExtension;
import org.hibernate.search.mapper.pojo.bridge.PropertyBridge;
import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext;
import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder;
import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext;
import org.hibernate.search.mapper.pojo.model.PojoModelProperty;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import java.util.UUID;

/** 只需要索引关联 Adminunit对象的ID字段，代替掉@IndexedEmbedded
 * 目标是提高性能：不要多余的sql查询语句，没必要查询每一个具体关联对象表。
 * 前提要求：Adminunit.id Town County  City Province Country.id的 ID类型保持一致性。
 * HS文档:  https://docs.jboss.org/hibernate/search/6.2/reference/en-US/html_single/
 * 索引Company用到了Adminunit ad：但是重建索引时刻，Company实体关联Unit unit是无法剔除的{HS更新依赖需要}，导致要求同时加载Unit实体，HS是无法避免Company join Unit查询。
 * 从Eqp索引重建测试来看：索引对象关联字段若是注解(fetch = FetchType.LAZY) 会更快点，若设置成EAGER策略反而更慢感觉数据库的并发程度低啊，都放入一条SQL语句。
 * */
public class AdminunitBinder implements PropertyBinder {
    //目标索引字段名。默认，下面替换为注解的实际字段名。
    private String fieldName = "ad";

    public AdminunitBinder fieldName(String fieldName) {
        this.fieldName = fieldName;
        return this;
    }

    @Override
    public void bind(PropertyBindingContext context) {
        context.dependencies()
                .use( "id" )
                .use( "town.id" )
                .use( "county.id" )
                .use( "city.id" )
                .use( "province.id" )
                .use( "country.id" );

        PojoModelProperty bridgedElement = context.bridgedElement();
        IndexSchemaObjectField adminunitField = context.indexSchemaElement()       //挂接关联索引根出处
                .objectField( bridgedElement.name() );          //实际字段名称；
        //针对Adminunit:"id"字段; Town County  City Province Country.id的 ID类型保持一致
        PojoModelProperty  idProperty= bridgedElement.property("id");
        boolean isLongType= idProperty.isAssignableTo(Long.class);

        IndexFieldType<?> idFieldType = null;          //<?>对应着(IndexFieldReference<？>)的类型；
        if(isLongType){
            idFieldType =(IndexFieldType<Long>)( context.typeFactory().asLong()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType() );
        }
        else {
            idFieldType =(IndexFieldType<String>)( context.typeFactory().asString()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType() );
        }
        //第二层挂接到上一层的：
        IndexSchemaObjectField townoField = adminunitField.objectField( "town" );
        IndexSchemaObjectField countyoField = adminunitField.objectField( "county" );
        IndexSchemaObjectField cityoField = adminunitField.objectField( "city" );
        IndexSchemaObjectField provinceoField = adminunitField.objectField( "province" );
        IndexSchemaObjectField countryoField = adminunitField.objectField( "country" );
        //嵌套的两层也需要在这里就得构建定义：Bridge《T》
        context.bridge( Node.class, new Bridge(
                adminunitField.toReference(),
                adminunitField.field( "id", idFieldType ).toReference(),
                isLongType? Long.class : UUID.class,
                townoField.toReference(),townoField.field( "id", idFieldType ).toReference(),
                countyoField.toReference(), countyoField.field( "id", idFieldType ).toReference(),
                cityoField.toReference(), cityoField.field( "id", idFieldType ).toReference(),
                provinceoField.toReference(), provinceoField.field( "id", idFieldType ).toReference(),
                countryoField.toReference(), countryoField.field( "id", idFieldType ).toReference()
                ) );
    }

/*若是输出索引对象字段有下面嵌套的有多个关联的字段：需要上面和下面配套多个参数的。
* */
    @SuppressWarnings("rawtypes")
    private static class Bridge<T>  implements PropertyBridge<Node> {

        private final IndexObjectFieldReference adminunitField;       //关联对象字段本身
        private final IndexFieldReference<?> adminunitIdField;       //关联对象字段底下的多个字段
        //ID的类型 Long 或 UUID
        private final Class<T>  idType;
        //第二层次的和关联节点：
        private final IndexObjectFieldReference townoField;
        private final IndexFieldReference<?> townIdField;
        private final IndexObjectFieldReference countyoField;
        private final IndexFieldReference<?> countyIdField;
        private final IndexObjectFieldReference cityoField;
        private final IndexFieldReference<?> cityIdField;
        private final IndexObjectFieldReference provinceoField;
        private final IndexFieldReference<?> provinceIdField;
        private final IndexObjectFieldReference countryoField;
        private final IndexFieldReference<?> countryIdField;

        private Bridge(IndexObjectFieldReference adminunitField,
                       IndexFieldReference<?> adminunitIdField,
                       Class<T> idType,
                       IndexObjectFieldReference townoField,IndexFieldReference<?> townIdField,
                       IndexObjectFieldReference countyoField,IndexFieldReference<?> countyIdField,
                       IndexObjectFieldReference cityoField,IndexFieldReference<?> cityIdField,
                       IndexObjectFieldReference provinceoField,IndexFieldReference<?> provinceIdField,
                       IndexObjectFieldReference countryoField,IndexFieldReference<?> countryIdField
                       ) {
            this.adminunitField = adminunitField;
            this.adminunitIdField = adminunitIdField;
            this.idType = idType;
            this.townoField = townoField;
            this.townIdField = townIdField;
            this.countyoField = countyoField;
            this.countyIdField = countyIdField;
            this.cityoField = cityoField;
            this.cityIdField = cityIdField;
            this.provinceoField = provinceoField;
            this.provinceIdField = provinceIdField;
            this.countryoField = countryoField;
            this.countryIdField = countryIdField;
        }

        /*针对是关联对象且 fetch= FetchType.LAZY)的才有意义的。 Adminunit类型bridgedElement的字段就不会自动查询关联对象了，提高性能。
         有缺点：字符串SQL语句出错，运行前可能无法提示错误。不改造默认实体装载的语句太烂长，浪费数据库查询能力。
         针对Company类做索引的:进入write（）时刻已经导致自动提取Unit语句生成了，无法避免！
        * */
        @Override
        public void write(DocumentElement target, Node bridgedElement, PropertyBridgeWriteContext context) {
            Session session = context.extension( HibernateOrmExtension.get() ).session();
            //可惜：Company类定义@OneToOne(mappedBy="company",fetch= FetchType.LAZY) private Unit unit;导致自动提取Unit语句生成！。
            //List<EntityGraph<? super Company>> list=session.getEntityGraphs(Company.class) 为空的[];
            T adminunitId= null!=bridgedElement? (T) bridgedElement.getId() : null;
            if(null==adminunitId)    return;

            //【注意】类的名字字段名修改，可能不会refactor自动同步修订这里的JPQL语句的，要手动改同步的！直接上select u.town_id,u.county_id,报错！hibernate.QueryException
            String jpql= """
select t.id,y.id,c.id,p.id,n.id from Adminunit u left join u.town t left join u.county y left join u.city c left join u.province p left join u.country n where u.id=:id""";
            //用这自定义的查询：就可以避免自动生成的实体装载的SQL语句的冗长，查了一堆没用的字段，关联了一堆实体表，司机对于当前我这里函数真的毫无作用啊，大大提高性能。
            //<R> Query<R> createQuery(String queryString, Class<R> resultClass);  ：这个位置就是没法用普通的Repository Units方式啊。也没法投影interface去查的。
            TypedQuery<Tuple>  simpleQuery= ((EntityManager) session).createQuery(jpql, Tuple.class);
            simpleQuery.setParameter("id",adminunitId);
            Tuple  rowDat= simpleQuery.getSingleResult();
            Object townId= rowDat.get(0);
            Object countyId= rowDat.get(1);
            Object cityId= rowDat.get(2);
            Object provinceId= rowDat.get(3);
            Object countryId= rowDat.get(4);

            DocumentElement top = target.addObject( this.adminunitField );
            if(Long.class==this.idType){
                top.addValue((IndexFieldReference<Long>) this.adminunitIdField, (Long)adminunitId );
                if(null!=townId)
                    top.addObject(this.townoField).addValue( (IndexFieldReference<Long>)this.townIdField,  (Long)townId);
                if(null!=countyId)
                    top.addObject(this.countyoField).addValue( (IndexFieldReference<Long>)this.countyIdField,  (Long)countyId);
                if(null!=cityId)
                    top.addObject(this.cityoField).addValue( (IndexFieldReference<Long>)this.cityIdField,  (Long)cityId);
                if(null!=provinceId)
                    top.addObject(this.provinceoField).addValue( (IndexFieldReference<Long>)this.provinceIdField,  (Long)provinceId);
                if(null!=countryId)
                    top.addObject(this.countryoField).addValue( (IndexFieldReference<Long>)this.countryIdField,  (Long)countryId);
            }
            else {
                top.addValue((IndexFieldReference<String>) this.adminunitIdField, adminunitId.toString() );
                if(null!=townId)
                    top.addObject(this.townoField).addValue( (IndexFieldReference<String>)this.townIdField,  townId.toString());
                if(null!=countyId)
                    top.addObject(this.countyoField).addValue( (IndexFieldReference<String>)this.countyIdField,  countyId.toString());
                if(null!=cityId)
                    top.addObject(this.cityoField).addValue( (IndexFieldReference<String>)this.cityIdField,  cityId.toString());
                if(null!=provinceId)
                    top.addObject(this.provinceoField).addValue( (IndexFieldReference<String>)this.provinceIdField,  provinceId.toString());
                if(null!=countryId)
                    top.addObject(this.countryoField).addValue( (IndexFieldReference<String>)this.countryIdField,  countryId.toString());
            }
        }
    }
}


/*旧的，
       "ad": {
          "dynamic": "strict",
          "properties": {
            "city": {
              "dynamic": "strict",
              "properties": {
                "id": {
                  "type": "keyword"
                }
              }
            },
            "country": {
              "dynamic": "strict",
              "properties": {
                "id": {
                  "type": "keyword"
                }
              }
            },
            "county": {
              "dynamic": "strict",
              "properties": {
                "id": {
                  "type": "keyword"
                }
              }
            },
            "id": {
              "type": "keyword"
            },
            "province": {
              "dynamic": "strict",
              "properties": {
                "id": {
                  "type": "keyword"
                }
              }
            },
            "town": {
              "dynamic": "strict",
              "properties": {
                "id": {
                  "type": "keyword"
                }
              }
            }
          }
        },
* */